package com.bupt.utils;

import com.bupt.annotation.MParam;
import com.bupt.exception.BaseException;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * 写入文件以及读取文件
 * 文件的基本操作 创建以及删除
 *
 * @author breakpoint/赵先生
 * 2020/09/03
 */
@Slf4j
public abstract class FileUtils {

    private static final Charset charset = StandardCharsets.UTF_8; // 默认编码方式

    private static final int ALLOCATION_SIZE = 10240; // 默认分配空间

    private static final Object WRITE_LOCK = new Object();

    // 将数据写入文件
    public static boolean writeBytesToFile(@MParam("二进制数据") byte[] bytes,
                                           @MParam("文件名") String fileName,
                                           @MParam("文件地址") String filePath) throws BaseException {
        if (null == bytes || bytes.length == 0) return false;
        try {
            LocalVerify.verifyStringIsNotNull(fileName, filePath);
        } catch (BaseException e) {
            return false;
        }
        File dir = new File(filePath);
        if (!dir.exists()) {
            if (!dir.mkdirs()) {
                throw new BaseException("请更改该文件夹[" + filePath + "]的权限，新建文件夹失败！！");
            }
        }
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(new File(dir, fileName));
            fos.write(bytes);
            fos.flush();
            // 保存成功的情况
            return true;
        } catch (Exception e) {
            return false;
        } finally {
            if (null != fos) {
                try {
                    fos.close();
                } catch (IOException e) {
                    //e.printStackTrace();
                }
            }
        }
    }

    // 从文件流里面读取我们的数据 注意 ：已经将文件流进行了关闭的操作
    // ** warn: inputStream 需要手动的关闭
    public static byte[] readBytesFromInputStream(@MParam("文件的输入流") InputStream inputStream) throws BaseException {
        if (null == inputStream) throw new BaseException("输入流不能为空");
        ByteArrayOutputStream bos = null;
        try {
            bos = new ByteArrayOutputStream();
            byte[] bytes = new byte[ALLOCATION_SIZE];
            int len = -1;
            while ((len = inputStream.read(bytes)) != -1) {
                bos.write(bytes, 0, len);
            }
            // 返回数据
            return bos.toByteArray();
        } catch (Exception e) {
            return null;
        } finally {
            if (null != bos) {
                try {
                    bos.close();
                } catch (IOException e) {
                    //e.printStackTrace();
                }
            }
        }// end finally

    }

    // 读取文件数据
    public static byte[] readBytesFromFile(@MParam("文件名") String fileName,
                                           @MParam("文件路径") String filePath) throws BaseException {
        LocalVerify.verifyStringIsNotNull(fileName, filePath);
        File file = new File(filePath, fileName);
        if (!file.exists()) return null;
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
            return readBytesFromInputStream(fis);
        } catch (Exception e) {
            return null;
        } finally {
            if (null != fis) {
                try {
                    fis.close();
                } catch (IOException e) {
                    //e.printStackTrace();
                }
            }
        }// end finally
    }

    // 复制文件的操作
    public static boolean copyFile(@MParam("源文件名称") String oriFileName, @MParam("源文件地址") String oriFilePath,
                                   @MParam("目标文件名字") String sinkFileName, @MParam("目标文件地址") String sinkFilePath)
            throws BaseException {
        LocalVerify.verifyString(oriFileName, "源文件名称");
        LocalVerify.verifyString(oriFilePath, "源文件地址");
        LocalVerify.verifyString(sinkFileName, "目标文件名称");
        LocalVerify.verifyString(sinkFilePath, "目标文件地址");
        byte[] bytes = readBytesFromFile(oriFileName, oriFilePath);
        if (null != bytes && bytes.length > 0) {
            try {
                writeBytesToFile(bytes, sinkFileName, sinkFilePath);
                return true;
            } catch (Exception e) {
                throw new BaseException("文件复制出现错误");
            }

        } else {
            throw new BaseException("源文件不存在");
        }
    }

    // 文件夹内部复制
    public static boolean copyFile(@MParam("源文件") String oriFileName, @MParam("文件路径") String filePath,
                                   @MParam("目标文件") String sinkFileName) throws BaseException {
        return copyFile(oriFileName, filePath, sinkFileName, filePath);
    }

    // 文件的删除操作
    public static boolean deleteFile(@MParam("文件名") String fileName, @MParam("文件路径") String filePath)
            throws BaseException {
        LocalVerify.verifyString(fileName, "fileName");
        LocalVerify.verifyString(filePath, "filePath");
        final File file = new File(filePath, fileName);
        if (file.exists()) {
            return file.delete();
        }
        return true;
    }

    // 文件的删除操作
    public static boolean deleteZipFile(@MParam("文件名") String fileName, @MParam("文件路径") String filePath)
            throws BaseException {
        LocalVerify.verifyString(fileName, "fileName");
        LocalVerify.verifyString(filePath, "filePath");
        final File file = new File(filePath, getZipFileName(fileName));
        if (file.exists()) {
            return file.delete();
        }
        return true;
    }

    // 删除文件
    public static boolean deleteFile(final File file)
            throws BaseException {
        LocalVerify.verifyObject(file, file.getName());
        return file.exists() && file.delete();
    }

    // 获压缩文件名字
    private static String getZipFileName(String fileName) {
        return String.format("%s.zip", fileName);
    }

    // 写入zip 文件
    public static  void zipWriteFile(String fileName, String filePath, Function<ZipOutputStream, List<Future<Boolean>>> consumer)
            throws BaseException {
        LocalVerify.verifyStringIsNotNull(fileName, filePath);
        File dir = new File(filePath);
        if (!dir.exists()) {
            if (!dir.mkdirs()) {
                throw new BaseException("请更改该文件夹[" + filePath + "]的权限，新建文件夹失败！！");
            }
        }
        ZipOutputStream zipOutputStream = null;
        try {
            zipOutputStream = new ZipOutputStream(
                    new FileOutputStream(new File(dir, getZipFileName(fileName)), false));
            List<Future<Boolean>> apply = consumer.apply(zipOutputStream);
            for (Future<Boolean> future : apply) {
                log.info("zip操作返回结果 【{}】", future.get());
            }
        } catch (Exception e) {
            throw new BaseException(e.getMessage());
        } finally {
            if (null != zipOutputStream) {
                try {
                    // 写入文件
                    zipOutputStream.flush();
                    zipOutputStream.close();
                } catch (IOException e) {
                    //e.printStackTrace();
                }
            }
        }
    }

    // 将数据写入一个 ZipEntry 注意并发的问题
    public synchronized static void zipWriteFile(ZipOutputStream outputStream, String fileName, String entryName, byte[] bytes)
            throws BaseException {
        try {
            ZipEntry zipEntry = new ZipEntry(entryName);
            outputStream.putNextEntry(zipEntry);
            zipEntry.setComment(String.format("%s_%s", fileName, entryName));
            outputStream.write(bytes);
        } catch (IOException e) {
            throw new BaseException(e.getMessage());
        }
    }

    // 写入单个文件  压缩文件
    public static boolean zipWriteFile(byte[] bytes, String fileName, String entryName, String filePath) throws BaseException {
        if (null == bytes || bytes.length == 0) return false;
        try {
            LocalVerify.verifyStringIsNotNull(fileName, filePath);
        } catch (BaseException e) {
            return false;
        }
        File dir = new File(filePath);
        if (!dir.exists()) {
            if (!dir.mkdirs()) {
                throw new BaseException("请更改该文件夹[" + filePath + "]的权限，新建文件夹失败！！");
            }
        }
        ZipOutputStream zipOutputStream = null;
        try {
            zipOutputStream = new ZipOutputStream(
                    new FileOutputStream(new File(dir, getZipFileName(fileName)), false));
            ZipEntry zipEntry = new ZipEntry(entryName);
            zipOutputStream.putNextEntry(zipEntry);
            zipEntry.setComment(String.format("%s_%s", fileName, entryName));
            zipOutputStream.write(bytes);
            return true;
        } catch (Exception e) {
            //e.printStackTrace();
            return false;
        } finally {
            if (null != zipOutputStream) {
                try {
                    // 写入文件
                    zipOutputStream.flush();
                    zipOutputStream.close();
                } catch (IOException e) {
                    //e.printStackTrace();
                }
            }
        }
    }

    // 获取恰当的前缀
    public static List<String> getEntryNameByPrefix(String fileName, String entryNamePrefix, String filePath) throws BaseException {
        List<String> res = new ArrayList<>();
        File file = new File(filePath, getZipFileName(fileName));
        if (!file.exists()) return res;
        ZipInputStream zipInputStream = null;
        try {
            zipInputStream = new ZipInputStream(new FileInputStream(file));
            ZipEntry nextEntry = zipInputStream.getNextEntry();
            while (null != nextEntry) {
                // 分类操作
                if (nextEntry.getName().contains(entryNamePrefix)) res.add(nextEntry.getName());
                nextEntry = zipInputStream.getNextEntry();
            }
        } catch (FileNotFoundException e) {
            throw new BaseException("文件没有找到");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != zipInputStream) {
                try {
                    zipInputStream.close();
                } catch (IOException e) {
                    //e.printStackTrace();
                }
            }
        }
        return res;
    }

    // 读取文件内容
    public static byte[] zipReadBytesFromFile(String fileName, String entryName, String filePath) throws BaseException {
        File file = new File(filePath, getZipFileName(fileName));
        if (!file.exists()) return null;
        ZipInputStream zipInputStream = null;
        try {
            zipInputStream = new ZipInputStream(new BufferedInputStream(new FileInputStream(file)));
            ZipEntry nextEntry = null;
            do {
                nextEntry = zipInputStream.getNextEntry();
            } while (null != nextEntry && !nextEntry.getName().equals(entryName));
            if (null != nextEntry) {
                return readBytesFromInputStream(zipInputStream);
            }
        } catch (FileNotFoundException e) {
            throw new BaseException("文件没有找到");
        } catch (IOException e) {
            throw new BaseException("读物文件异常" + e.getMessage());
        } finally {
            if (null != zipInputStream) {
                try {
                    zipInputStream.close();
                } catch (IOException e) {
                    //e.printStackTrace();
                }
            }
        }
        return null;
    }

    /*
            增加新的方法：
     */

    // 获取字符串重文件中
    public static String getStringFromFile(@MParam("文件名") String fileName, @MParam("文件路径") String filePath) throws BaseException {
        return getStringFromFile(filePath + File.separator + fileName);
    }

    // 获取字符串重文件中
    public static String getStringFromFile(@MParam("文件名") String absolutePath) {
        StringBuilder jsonStr = new StringBuilder();
        Reader reader = null;
        try {
            // 获取到文件
            File file = new File(absolutePath);
            if (!file.exists()) return null;
            reader = new InputStreamReader(new FileInputStream(file), "utf-8");
            int ch = 0;
            while ((ch = reader.read()) != -1) {
                jsonStr.append((char) ch);
            }
            return jsonStr.toString();
        } catch (IOException e) {
            log.error("文件读取失败 [{}]", e.getMessage());
            return null;
        } finally {
            if (null != reader) {
                try {
                    reader.close();
                } catch (IOException e) {
                    // nothing to do
                }
            }
        }
    }

}
